/*
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * This file is part of the Contiki operating system.
 *
 */

#include "contiki.h"
#include "contiki-lib.h"
#include "contiki-net.h"
#include "net/ip/uip.h"
#include "net/rpl/rpl.h"

#include "net/netstack.h"
#include "dev/button-sensor.h"
#include "ffz/ffzconfig.h"
#include "ffz/ffz-collect.h"
#include "ffz/raft.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define DEBUG DEBUG_PRINT
#include "ffz/debug.h"

#include "sys/clock.h"

#define UIP_IP_BUF ((struct uip_ip_hdr *)&uip_buf[UIP_LLH_LEN])

#define UDP_SERVER 0

static struct uip_udp_conn *recv_conn;
static struct uip_udp_conn *send_conn;
static uip_ipaddr_t server_ipaddr;

PROCESS(udp_server_process, "UDP server process");
PROCESS(btn_process, "Button process");
PROCESS(role_process, "Follower process");
static uint16_t broadCastCursor = 0;
AUTOSTART_PROCESSES(&udp_server_process, &btn_process, &role_process);
/*---------------------------------------------------------------------------*/
static void
tcpip_handler(void)
{
  char *appdata;

  if (uip_newdata())
  {
    appdata = (char *)uip_appdata;
#if UDP_SERVER == 1
    linkaddr_t sender;
    uint8_t seqno;
    uint8_t hops;
    // appdata[uip_datalen()] = 0;
    // PRINTF("DATA recv '%s' from ", appdata);
    // PRINTF("%d",
    //        UIP_IP_BUF->srcipaddr.u8[sizeof(UIP_IP_BUF->srcipaddr.u8) - 1]);
    // PRINTF("\n");
#endif

    PRINTF("UIP_IP_BUF->srcipaddr.u8 == ");
    PRINT6ADDR(UIP_IP_BUF->srcipaddr.u8);
    PRINTF(" %s ", appdata);
    PRINTF("\n");
    raftHandler(UIP_IP_BUF->srcipaddr.u8, uip_appdata, uip_datalen());
  }
}

/*---------------------------------------------------------------------------*/
PROCESS_THREAD(udp_server_process, ev, data)
{
  uip_ipaddr_t ipaddr;
  struct uip_ds6_addr *root_if;

  PROCESS_BEGIN();

  PROCESS_PAUSE();

  // SENSORS_ACTIVATE(button_sensor);

  PRINTF("UDP server started\n");

#if UIP_CONF_ROUTER
  /* The choice of server address determines its 6LoPAN header compression.
 * Obviously the choice made here must also be selected in udp-client.c.
 *
 * For correct Wireshark decoding using a sniffer, add the /64 prefix to the 6LowPAN protocol preferences,
 * e.g. set Context 0 to aaaa::.  At present Wireshark copies Context/128 and then overwrites it.
 * (Setting Context 0 to aaaa::1111:2222:3333:4444 will report a 16 bit compressed address of aaaa::1111:22ff:fe33:xxxx)
 * Note Wireshark's IPCMV6 checksum verification depends on the correct uncompressed addresses.
 */

#if 0
/* Mode 1 - 64 bits inline */
   uip_ip6addr(&ipaddr, 0xaaaa, 0, 0, 0, 0, 0, 0, 1);
#elif 1
  /* Mode 2 - 16 bits inline */
  uip_ip6addr(&ipaddr, 0xaaaa, 0, 0, 0, 0, 0x00ff, 0xfe00, 1);
#else
  /* Mode 3 - derived from link local (MAC) address */
  uip_ip6addr(&ipaddr, 0xaaaa, 0, 0, 0, 0, 0, 0, 0);
  uip_ds6_set_addr_iid(&ipaddr, &uip_lladdr);
#endif

uip_ds6_addr_add(&ipaddr, 0, ADDR_MANUAL);
root_if = uip_ds6_addr_lookup(&ipaddr);
#if UDP_SERVER == 1
  if (root_if != NULL)
  {
    rpl_dag_t *dag;
    dag = rpl_set_root(RPL_DEFAULT_INSTANCE, (uip_ip6addr_t *)&ipaddr);
    uip_ip6addr(&ipaddr, 0xaaaa, 0, 0, 0, 0, 0, 0, 0);
    rpl_set_prefix(dag, &ipaddr, 64);
    PRINTF("created a new RPL dag\n");
  }
  else
  {
    PRINTF("failed to create a new RPL DAG\n");
  }
#endif

#endif /* UIP_CONF_ROUTER */

  print_local_addresses_and_set_id();

  /* The data sink runs with a 100% duty cycle in order to ensure high 
     packet reception rates. */
  NETSTACK_MAC.off(1);
  raftNodeTableInit(NODE_NUM);

  recv_conn = udp_new(NULL, UIP_HTONS(UDP_CLIENT_PORT), NULL);
  if (recv_conn == NULL)
  {
    PRINTF("No UDP connection available, exiting the process!\n");
    PROCESS_EXIT();
  }
  udp_bind(recv_conn, UIP_HTONS(UDP_SERVER_PORT));

  /* new connection with remote host */
  send_conn = udp_new(NULL, UIP_HTONS(UDP_SERVER_PORT), NULL);
  if (send_conn == NULL)
  {
    PRINTF("No UDP connection available, exiting the process!\n");
    PROCESS_EXIT();
  }
  udp_bind(send_conn, UIP_HTONS(UDP_CLIENT_PORT));

  // PRINTF("Created a server connection with remote address ");
  // PRINT6ADDR(&recv_conn->ripaddr);
  // PRINTF(" local/remote port %u/%u\n", UIP_HTONS(recv_conn->lport), UIP_HTONS(recv_conn->rport));
  initSendConnection(send_conn);
  initRecvConnection(recv_conn);

  // #if UDP_SERVER == 0
  //   // uip_ip6addr(&server_ipaddr, 0xfe80, 0, 0, 0, 0x0212, 0x7401, 1, 0x0101);
  //   uip_ip6addr(&server_ipaddr, 0xaaaa, 0, 0, 0, 0x0212, 0x7401, 1, 0x0101);
  //   collect_common_net_init(send_conn, &server_ipaddr);
  // #endif

  while (1)
  {
    PROCESS_YIELD();
    if (ev == tcpip_event)
    {
      tcpip_handler();
    }
  }

  PROCESS_END();
}
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(btn_process, ev, data)
{
  static struct etimer button_timer;
  // static char send_data[32];
  // uip_ipaddr_t dest_ipaddr;
  PROCESS_BEGIN();

  /* Setup a periodic timer that expires after 10 seconds. */
  etimer_set(&button_timer, CLOCK_SECOND * 4);
  SENSORS_ACTIVATE(button_sensor);
  printf("btn_process started......\n");
  while (1)
  {
    PROCESS_YIELD();
    if (ev == sensors_event && data == &button_sensor)
    {
      int count = button_click();
      if (count == 1)
      {
        // printf("RESET TIMER\n");
        etimer_restart(&button_timer);
        // process_post(&role_process, EVENT_CANDIDATE_RESET, NULL);
      }
      printf("action select %s\n", get_action_name(count));
    }
    else if (ev == EVENT_ETIMER)
    {
      int count = button_clear();
      if (count == 1)
      {
        printf("action use %s\n", get_action_name(count));
        uip_ipaddr_t addr;
        uint8_t target = 0;
        PRINT6ADDR(&getRaftNodeTableItem(target)->ip);
        PRINTF(" --- send to \n");
        // raftPacketSendBroadcast("123456", 6);
        raftPacketSendUnicast("123", 3, &getRaftNodeTableItem(target)->ip);
      }
      else if (count == 2)
      {
        printf("action use %s\n", get_action_name(count));
        etimer_reset(&button_timer);
        rpl_repair_root(RPL_DEFAULT_INSTANCE);
        /* DEBUG the broadcast */
        // createRaftPacketCandidate(0, getRaftTableLocalId(), 0, 0, getSendBuffer());
        // raftPacketSendBroadcast(getSendBuffer(), 9);
      }
    }
  }
  PROCESS_END();
}
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(role_process, ev, data)
{
  static struct etimer follower_timer;
  PROCESS_BEGIN();
  PROCESS_END();
}